﻿using NUnit.Framework;
using Sasa.QualityTools.CodeAnalysis.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sasa.QualityTools.CodeAnalysis.Core.Tests.Unit.Instruments;
using Sasa.QualityTools.CodeAnalysis.Core.VisualStudio;
using System.IO;
using Sasa.QualityTools.CodeAnalysis.Core.Models;

namespace Sasa.QualityTools.CodeAnalysis.Core.Tests.Unit.Utilities
{
    [TestFixture]
    public class FxCopCmdOptionBuilderTest
    {
        [TestFixtureTearDown]
        public void TearDownFixture()
        {
            TestCaseDataGenerator.Cleanup();
        }

        [Test]
        [TestCaseSource(typeof(TestCaseDataGenerator), "GenerateForBuildWithSolution")]
        public string BuildWithSolutionShouldWorkCorrectly(ISolution solution)
        {
            return FxCopCmdOptionBuilder.Build(solution);
        }

        [Test]
        [TestCaseSource(typeof(TestCaseDataGenerator), "GenerateForBuildWithProject")]
        public string BuildWithProjectShouldWorkCorrectly(IProject project, Version version, params string[] typeNames)
        {
            return FxCopCmdOptionBuilder.Build(project, version, typeNames);
        }


        public class TestCaseDataGenerator
        {
            private static readonly string ResourceDir = Path.Combine(Environment.CurrentDirectory, "Resources", "Utilities", "FxCopCmdOptionBuilder");


            public static void Cleanup()
            {
                foreach (var path in Directory.GetFiles(ResourceDir, "*.conf"))
                {
                    File.Delete(path);
                }
            }

            public IEnumerable<TestCaseData> GenerateForBuildWithSolution()
            {
                yield return new TestCaseData(null)
                    .Throws(typeof(ArgumentNullException))
                    .SetName("Build should throw exception when solution is null.");

                yield return new TestCaseData(new DummySolution { FxCopProjectPath = "xxx.fxcop", ConfigurationDir = "conf" })
                    .Returns(@"/p:""xxx.fxcop"" /o:""conf\CodeAnalysisReport.xml"" /sf /fo")
                    .SetName("Build should not set /dic option when the dictionary file doesn't exist.");

                var dictionaryPath = Path.Combine(ResourceDir, "FxCopCustomDictionary.xml");
                yield return new TestCaseData(new DummySolution { FxCopProjectPath = "xxx.fxcop", ConfigurationDir = "conf", FxCopDictionaryPath = dictionaryPath })
                    .Returns(@"/p:""xxx.fxcop"" /o:""conf\CodeAnalysisReport.xml"" /sf /fo /dic:""" + dictionaryPath + @"""")
                    .SetName("Build should set /dic option when the dictionary file exists.");
            }

            public IEnumerable<TestCaseData> GenerateForBuildWithProject()
            {
                yield return new TestCaseData(null, new Version(), new string[] { })
                    .Throws(typeof(ArgumentNullException))
                    .SetName("Build should throw exception when the project is null.");

                yield return new TestCaseData(new DummyProject(), null, new string[] { })
                    .Throws(typeof(ArgumentNullException))
                    .SetName("Build should throw exception when the version is null.");

                yield return new TestCaseData(new DummyProject(), new Version(), null)
                    .Throws(typeof(ArgumentNullException))
                    .SetName("Build should throw exception when the typeNames is null.");

                yield return new TestCaseData(BuildupProject(timeout: 200), new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:200")
                    .SetName("Build should set the specified timeout to /to option when it is greater than 0.");

                yield return new TestCaseData(BuildupProject(timeout: -1), new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0")
                    .SetName("Build should set 0 to /to option when it is less than 0.");

                yield return new TestCaseData(BuildupProject(enableSearchGac: true), new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /gac")
                    .SetName("Build should set /gac option when EnableSearchGac of configuration is true.");

                yield return new TestCaseData(BuildupProject(ignoreGeneratedCode: true), new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /igc")
                    .SetName("Build should set /igc option when IgnoreGeneratedCode of configuration is true.");

                yield return new TestCaseData(BuildupProject(), new Version(10, 0), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0")
                    .SetName("Build should not set /rs option when FxCop version is greater than 10 and Ruleset file doesn't exist.");

                var ruleset = Path.Combine(ResourceDir, "Custom.ruleset");
                var bundleDir = Path.GetFullPath(Path.Combine(Environment.GetEnvironmentVariable("VS100COMNTOOLS"), "..", "..", "Team Tools", "Static Analysis Tools", "Rule Sets"));
                yield return new TestCaseData(BuildupProject(ruleset: ruleset), new Version(10, 0), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /rs:=""" + ruleset + @""" /rsd:""" + bundleDir + @"""")
                    .SetName("Build should set /rs and /rsd option when FxCop version is greater than 10 and Ruleset file exists.");

                var dictionary = Path.Combine(ResourceDir, "FxCopCustomDictionary.xml");
                yield return new TestCaseData(BuildupProject(dictionary: dictionary), new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /dic:""" + dictionary + @"""")
                    .SetName("Build should set /dic option when FxCop dictionary file exists.");

                var project = BuildupProject();
                project.DependencyDirectories = new List<string> { "a", "b", "c" };
                yield return new TestCaseData(project, new Version(), new string[] { })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /d:""a"" /d:""b"" /d:""c""")
                    .SetName("Build should set /d options when IProject has dependency directories.");

                yield return new TestCaseData(BuildupProject(), new Version(), new string[] { "a", "b", "c" })
                    .Returns(@"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /t:a,b,c")
                    .SetName("Build should set /t option when type names are specified");

                yield return BuildShouldRemoveLastBackslashesOfDependencyDirectories();
            }

            private TestCaseData BuildShouldRemoveLastBackslashesOfDependencyDirectories()
            {
                var project = BuildupProject();
                project.DependencyDirectories = new List<string> { "a", "a\\b", "a\\b\\", "a\\b\\c\\\\\\" };
                var version = new Version();
                var typeNames = new string[]{};
                var result = @"/f:""asm.dll"" /o:""bin\CodeAnalysisReport.xml"" /c /sf /fo /to:0 /d:""a"" /d:""a\b"" /d:""a\b"" /d:""a\b\c""";
                var name = "Build should remove last backslashes of dependency directories.";
                return new TestCaseData(project, version, typeNames).Returns(result).SetName(name);
            }

            private DummyProject BuildupProject(bool enableSearchGac = false, bool ignoreGeneratedCode = false, double timeout = 0, string ruleset = null, string dictionary = null)
            {
                var configPath = Path.Combine(ResourceDir, Guid.NewGuid().ToString() + ".conf");
                var config = CodeAnalysisConfiguration.Load(configPath);
                config.EnableSearchGac = enableSearchGac;
                config.IgnoreGeneratedCode = ignoreGeneratedCode;
                config.Timeout = timeout;
                config.RuleSet = ruleset;
                config.Save();

                return new DummyProject
                {
                    AssemblyPath = "asm.dll",
                    TargetDir = "bin",
                    ConfigurationPath = "dummy.xml",
                    Solution = new DummySolution
                    {
                        ConfigurationPath = configPath,
                        FxCopDictionaryPath = dictionary
                    },
                };
            }
        }
    }
}
